home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Workbench Add-On
/
Workbench Add-On - Volume 1.iso
/
DiskUtil
/
CD-ROM
/
CDDA
/
CDDA.c
< prev
next >
Wrap
C/C++ Source or Header
|
1994-01-30
|
34KB
|
1,672 lines
;/*
SC PARAMS=REGISTER NMINC MCCONS STREQ NOWVRET STRMERGE NOSTKCHK UTILLIB OPTIMIZE OPTTIME OPTINLINE MODIFIED IGNORE=73 CDDA.c
SLINK CDDA.o TO CDDA LIB LIB:sc.lib LIB:amiga.lib SC SD ND NOICONS
Quit
*/
/*
** CDDA - A program to replay digital audio data read from a
** Sony CD-ROM drive.
**
** Copyright © 1993-1994 by Olaf `Olsen' Barthel
** This is a freeware release.
*/
/* Avoid a nasty type conflict in older include files. */
#define CheckIO foo
/* System includes */
#include <devices/scsidisk.h>
#include <devices/trackdisk.h>
#include <devices/audio.h>
#include <exec/execbase.h>
#include <exec/memory.h>
#include <dos/dosextens.h>
#include <dos/dostags.h>
#include <dos/rdargs.h>
#include <dos/dosasl.h>
#include <hardware/cia.h>
#include <clib/utility_protos.h>
#include <clib/exec_protos.h>
#include <clib/dos_protos.h>
#include <clib/alib_protos.h>
#include <clib/macros.h>
#include <pragmas/utility_pragmas.h>
#include <pragmas/exec_pragmas.h>
#include <pragmas/dos_pragmas.h>
#include <string.h>
/* Get the CheckIO prototype right. */
#undef CheckIO
struct IORequest *CheckIO(struct IORequest *);
/* The CIA A base address. */
#ifndef ciaa
extern struct CIA __far ciaa;
#endif /* ciaa */
/* Command line template. */
#define TEMPLATE "DEVICE/K,UNIT/K/N,TRACK/N,FROM/K/N,TO/K/N,NUM/K/N,MAX/K/N,VERBOSE/S"
/* The argument vector offsets. */
enum { ARG_DEVICE,ARG_UNIT,ARG_TRACK,ARG_FROM,ARG_TO,ARG_NUM,ARG_MAX,ARG_VERBOSE,
ARGCOUNT
};
/* Player task priority and stack size. */
#define CHILD_PRI 5
#define CHILD_STACK 8192
/* Default SCSI device name and unit number. */
#define SCSI_DEVICE "scsi.device"
#define SCSI_UNIT 2
/* The signal mask associated with a MsgPort. */
#define PORTMASK(p) (1L << ((struct MsgPort *)(p)) -> mp_SigBit)
/* Some signal masks. */
#define SIG_HANDSHAKE SIGF_SINGLE
#define SIG_KILL SIGBREAKF_CTRL_C
#define SIG_SYNC SIGBREAKF_CTRL_F
#define SIG_CHILD PORTMASK(ChildPort)
/* The size of an audio data buffer and the
* number of blocks per packet (= 1 second of data).
*/
#define BUFFER_SIZE 22050
#define BLOCKS_PER_PKT 75
/* Amiga audio channel allocation bits. */
#define LEFT0F 1
#define RIGHT0F 2
#define RIGHT1F 4
#define LEFT1F 8
/* SCSI command `test unit ready'. */
struct TestUnitReady
{
UBYTE Command,
Pad,
Reserved[3],
Control;
};
/* SCSI command `inquiry'. */
struct Inquiry
{
UBYTE Command,
Pad,
PageCode,
Reserved,
AllocationLength,
Control;
};
/* The data structure to be filled by the inquiry command. */
struct InquiryData
{
UBYTE PeripheralType,
DeviceTypeModifier,
Version,
Reserved[5],
Vendor[8],
Product[16],
Revision[4];
};
/* SCSI command `read table of contents'. */
struct ReadTOC
{
UBYTE Command,
Pad,
Reserved[4],
StartingTrack;
UBYTE Alloc1,Alloc2;
UBYTE Control;
};
/* The data structure to be filled in by the
* readtoc command.
*/
struct FullTOC
{
UWORD DataLength;
UBYTE FirstTrack,
LastTrack;
struct
{
UBYTE Reserved1,
Control,
TrackNumber,
Reserved2;
ULONG Address;
} TOC[100];
};
/* Sony vendor unique command to read digital
* audio data.
*/
struct ReadCDDA
{
UBYTE Command,
Pad;
ULONG Address,
Count;
UBYTE SubCode,
Control;
};
/* Digital audio data, as returned by the corresponding command. */
struct CDDASector
{
struct CDDASample
{
UBYTE LeftLSB,
LeftMSB,
RightLSB,
RightMSB;
} Sample[588];
};
/* Handler->player task communications structure. */
struct AudioMessage
{
struct Message Message;
LONG NumBlocks;
struct CDDASector *Data;
};
/* Global library bases. */
struct ExecBase *SysBase;
struct DosLibrary *DOSBase;
struct Library *UtilityBase;
/* Command line arguments. */
struct RDArgs *ArgsPtr;
STRPTR *Arg;
/* Audio data. */
struct IOAudio *AudioControl,
*AudioLeft,
*AudioRight;
struct MsgPort *AudioPort;
BYTE *ThisLeft,
*NextLeft,
*ThisRight,
*NextRight,
*AudioData;
ULONG Rate;
LONG FillCounter = 0,
TotalPending = 0;
BOOL AudioUsed = FALSE,
AudioActive = FALSE,
Verbose,
LED;
/* Current sector and total number of sectors to read. */
LONG Index,Total;
/* SCSI device control data. */
struct MsgPort *SCSIPort;
struct IOExtTD *SCSIRequest;
struct SCSICmd SCSICmd;
UBYTE SenseBuffer[256];
/* Data to be read from the device. */
struct FullTOC FullTOC;
struct InquiryData InquiryData;
/* Block queue access semaphore and number of
* packets in the queue.
*/
struct SignalSemaphore QueueSemaphore;
LONG QueueCount;
/* Handler and player task data. */
struct Task *Child;
struct MsgPort *ChildPort;
struct Process *Father;
BOOL FatherWaiting;
/* Program version ID. */
STRPTR VersionTag = "\0$VER: CDDA 1.3 (14.1.94)";
/* Prototypes for this module. */
LONG __saveds Main(VOID);
VOID SCSIExit(VOID);
BOOL SCSIInit(STRPTR Device,LONG Unit);
VOID AudioExit(VOID);
BOOL AudioInit(VOID);
VOID DeleteAudioMessage(struct AudioMessage *Message);
struct AudioMessage * CreateAudioMessage(LONG NumBlocks);
VOID StartAudio(VOID);
VOID ProcessAudio(VOID);
VOID __saveds ChildTask(VOID);
VOID CloseAll(VOID);
BOOL OpenAll(VOID);
BYTE ReadCDDASectors(APTR Buffer,LONG From,LONG Count);
BYTE TestUnitReady(VOID);
BYTE ReadTOC(VOID);
BYTE Inquire(VOID);
/* Main():
*
* The program entry point. No compiler startup code.
*/
LONG __saveds
Main()
{
LONG Result = RETURN_FAIL;
/* Open the resources. */
if(OpenAll())
{
LONG Blocks,MaxQueueSize;
struct AudioMessage *Message;
BOOL Stop = FALSE,
FirstMessage = TRUE;
/* Everything went fine so far. */
Result = RETURN_OK;
/* Get the maximum block queue size. */
if(Arg[ARG_MAX])
MaxQueueSize = *(LONG *)Arg[ARG_MAX];
else
MaxQueueSize = 3;
/* Safety check. */
if(MaxQueueSize < 1)
MaxQueueSize = 1;
/* Print the banner message. */
FPrintf(Output(),"\33[1mCDDA\33[0m © Copyright 1993-1994 by Olaf `Olsen' Barthel, \33[4mAll rights reserved\33[0m\n");
/* Play the tracks. */
while(Total > 0)
{
/* Stop playing? */
if(CheckSignal(SIG_KILL))
{
Stop = TRUE;
break;
}
/* Gain access to the block queue... */
ObtainSemaphore(&QueueSemaphore);
/* More blocks in the queue than there
* should be?
*/
if(QueueCount > MaxQueueSize)
{
/* Wait for the player task
* to make room.
*/
Forbid();
ReleaseSemaphore(&QueueSemaphore);
SetSignal(0,SIG_HANDSHAKE);
FatherWaiting = TRUE;
Wait(SIG_HANDSHAKE);
FatherWaiting = FALSE;
Permit();
}
else
ReleaseSemaphore(&QueueSemaphore);
/* Allocate another packet. */
if(Message = CreateAudioMessage(Blocks = MIN(BLOCKS_PER_PKT,Total)))
{
/* Read the data, we may need more than one
* attempt if the drive feels unable to supply
* the requested data immediately.
*/
while(ReadCDDASectors(Message -> Data,Index,Blocks))
{
/* Stop here? */
if(CheckSignal(SIG_KILL))
{
Stop = TRUE;
break;
}
/* If in verbose mode, tell the user that
* we are retrying to read the data.
*/
if(Verbose)
{
if(FirstMessage)
{
Printf("CDDA: Rereading sector #%ld\n",Index);
FirstMessage = FALSE;
}
else
Printf("\033[ACDDA: Rereading sector #%ld\033[K\n",Index);
}
}
/* Read request aborted? */
if(Stop)
{
/* Clean up and exit. */
DeleteAudioMessage(Message);
break;
}
else
{
/* Add the data to the queue. */
PutMsg(ChildPort,&Message -> Message);
Total -= Blocks;
Index += Blocks;
}
}
else
{
/* Stop here? */
if(CheckSignal(SIG_KILL))
{
Stop = TRUE;
break;
}
/* If in verbose mode tell the user that
* we will need to retry the memory
* allocation.
*/
if(Verbose)
{
if(FirstMessage)
{
Printf("CDDA: Retrying memory allocation for sector #%ld\n",Index);
FirstMessage = FALSE;
}
else
Printf("\033[ACDDA: Retrying memory allocation for sector #%ld\033[K\n",Index);
}
/* Wait for the player task to make room. */
Forbid();
SetSignal(0,SIG_HANDSHAKE);
FatherWaiting = TRUE;
Wait(SIG_HANDSHAKE);
FatherWaiting = FALSE;
Permit();
}
}
/* Issue the break message if necessary. */
if(Stop)
PrintFault(ERROR_BREAK,"CDDA");
else
{
/* Resync with player task, we
* are about to exit cleanly.
* In this case we want the player
* task to process the remaining
* audio data before we proceed to
* tell it to shut down.
*/
Forbid();
SetSignal(0,SIG_HANDSHAKE);
Signal(Child,SIG_SYNC);
Wait(SIG_HANDSHAKE);
Permit();
}
}
CloseAll();
return(Result);
}
/* SCSIExit():
*
* Cleans up the scsi device data.
*/
VOID
SCSIExit()
{
if(SCSIRequest)
{
if(SCSIRequest -> iotd_Req . io_Device)
CloseDevice(SCSIRequest);
DeleteIORequest(SCSIRequest);
SCSIRequest = NULL;
}
if(SCSIPort)
{
DeleteMsgPort(SCSIPort);
SCSIPort = NULL;
}
}
/* SCSIInit(STRPTR Device,LONG Unit):
*
* Sets up the scsi device data.
*/
BOOL
SCSIInit(STRPTR Device,LONG Unit)
{
if(!(SCSIPort = CreateMsgPort()))
return(FALSE);
if(!(SCSIRequest = (struct IOExtTD *)CreateIORequest(SCSIPort,sizeof(struct IOExtTD))))
return(FALSE);
if(OpenDevice(Device,Unit,SCSIRequest,NULL))
return(FALSE);
return(TRUE);
}
/* AudioExit():
*
* Cleans up the audio device data.
*/
VOID
AudioExit()
{
/* Turn the power LED (= alias filter) back on/off. */
Disable();
if(LED)
ciaa . ciapra &= ~CIAF_LED;
else
ciaa . ciapra |= CIAF_LED;
Enable();
/* Clean up the left audio channel data. */
if(AudioLeft)
{
if(AudioLeft -> ioa_Request . io_Device && AudioUsed)
{
if(!CheckIO(AudioLeft))
{
AbortIO(AudioLeft);
WaitIO(AudioLeft);
}
else
GetMsg(AudioPort);
}
FreeVec(AudioLeft);
AudioLeft = NULL;
}
/* Clean up the right audio channel data. */
if(AudioRight)
{
if(AudioRight -> ioa_Request . io_Device && AudioUsed)
{
if(!CheckIO(AudioRight))
{
AbortIO(AudioRight);
WaitIO(AudioRight);
}
else
GetMsg(AudioPort);
}
FreeVec(AudioRight);
AudioRight = NULL;
}
/* Free the control request. */
if(AudioControl)
{
if(AudioControl -> ioa_Request . io_Device)
CloseDevice(AudioControl);
DeleteIORequest(AudioControl);
AudioControl = NULL;
}
/* Take care of the rest. */
if(AudioPort)
{
DeleteMsgPort(AudioPort);
AudioPort = NULL;
}
if(AudioData)
{
FreeVec(AudioData);
AudioData = NULL;
}
}
/* AudioInit():
*
* Initialize the audio device driver data.
*/
BOOL
AudioInit()
{
/* Audio channel allocation map which should
* allocate two different stereo channels.
*/
STATIC UBYTE AllocationMap[] =
{
LEFT0F | RIGHT0F,
LEFT0F | RIGHT1F,
LEFT1F | RIGHT0F,
LEFT1F | RIGHT1F
};
/* Turn off the power LED (= alias filter). */
Disable();
if(ciaa . ciapra & CIAF_LED)
LED = FALSE;
else
{
LED = TRUE;
ciaa . ciapra |= CIAF_LED;
}
Enable();
/* Allocate the data. */
if(!(AudioData = (BYTE *)AllocVec(BUFFER_SIZE * 4,MEMF_CHIP | MEMF_CLEAR)))
return(FALSE);
if(!(AudioPort = CreateMsgPort()))
return(FALSE);
if(!(AudioControl = (struct IOAudio *)CreateIORequest(AudioPort,sizeof(struct IOAudio))))
return(FALSE);
if(!(AudioLeft = (struct IOAudio *)AllocVec(sizeof(struct IOAudio),MEMF_PUBLIC)))
return(FALSE);
if(!(AudioRight = (struct IOAudio *)AllocVec(sizeof(struct IOAudio),MEMF_PUBLIC)))
return(FALSE);
/* Prepare for initialization. */
AudioControl -> ioa_Request . io_Message . mn_Node . ln_Pri = 127;
AudioControl -> ioa_Request . io_Command = ADCMD_ALLOCATE;
AudioControl -> ioa_Request . io_Flags = ADIOF_NOWAIT | ADIOF_PERVOL;
AudioControl -> ioa_Data = AllocationMap;
AudioControl -> ioa_Length = sizeof(AllocationMap);
/* Open the audio device driver. */
if(OpenDevice(AUDIONAME,NULL,AudioControl,NULL))
return(FALSE);
/* Choose the right replay rate for 22,050 samples per second. */
Rate = (SysBase -> ex_EClockFrequency * 5) / 22050;
/* Clone the audio control request. */
CopyMem(AudioControl,AudioLeft, sizeof(struct IOAudio));
CopyMem(AudioControl,AudioRight,sizeof(struct IOAudio));
/* Split the channels. */
AudioLeft -> ioa_Request . io_Unit = (struct Unit *)((ULONG)AudioLeft -> ioa_Request . io_Unit & (LEFT0F | LEFT1F));
AudioRight -> ioa_Request . io_Unit = (struct Unit *)((ULONG)AudioRight -> ioa_Request . io_Unit & (RIGHT0F | RIGHT1F));
/* Set up the double buffering data. */
ThisLeft = AudioData;
NextLeft = ThisLeft + BUFFER_SIZE;
ThisRight = NextLeft + BUFFER_SIZE;
NextRight = ThisRight + BUFFER_SIZE;
return(TRUE);
}
/* DeleteAudioMessage(struct AudioMessage *Message):
*
* Delete an AudioMessage, clean up the associated data.
*/
VOID
DeleteAudioMessage(struct AudioMessage *Message)
{
FreeVec(Message);
ObtainSemaphore(&QueueSemaphore);
QueueCount--;
ReleaseSemaphore(&QueueSemaphore);
/* Wake up the handler process. */
if(FatherWaiting)
Signal(Father,SIG_HANDSHAKE);
}
/* CreateAudioMessage(LONG NumBlocks):
*
* Create an AudioMessage, i.e. a message with auxilary data
* space attached.
*/
struct AudioMessage *
CreateAudioMessage(LONG NumBlocks)
{
struct AudioMessage *Message;
if(Message = (struct AudioMessage *)AllocVec(sizeof(struct AudioMessage) + 15 + NumBlocks * sizeof(struct CDDASector),MEMF_ANY | MEMF_PUBLIC))
{
ULONG Place = ((ULONG)(Message + 1) + 15) & ~15;
Message -> Message . mn_Length = sizeof(struct AudioMessage) + 15 + NumBlocks * sizeof(struct CDDASector);
Message -> NumBlocks = NumBlocks;
Message -> Data = (struct CDDASector *)Place;
ObtainSemaphore(&QueueSemaphore);
QueueCount++;
ReleaseSemaphore(&QueueSemaphore);
}
return(Message);
}
/* StartAudio():
*
* Set up the audio device requests to play the currently
* available data and start them simultaneously.
*/
VOID
StartAudio()
{
/* So nobody will interrupt us. */
Forbid();
AudioLeft -> ioa_Request . io_Command = CMD_WRITE;
AudioLeft -> ioa_Request . io_Flags = ADIOF_PERVOL;
AudioLeft -> ioa_Period = Rate;
AudioLeft -> ioa_Volume = 64;
AudioLeft -> ioa_Cycles = 1;
AudioLeft -> ioa_Data = ThisLeft;
AudioLeft -> ioa_Length = FillCounter;
/* Swap the buffers (double buffering). */
ThisLeft = NextLeft;
NextLeft = AudioLeft -> ioa_Data;
AudioRight -> ioa_Request . io_Command = CMD_WRITE;
AudioRight -> ioa_Request . io_Flags = ADIOF_PERVOL;
AudioRight -> ioa_Period = Rate;
AudioRight -> ioa_Volume = 64;
AudioRight -> ioa_Cycles = 1;
AudioRight -> ioa_Data = ThisRight;
AudioRight -> ioa_Length = FillCounter;
/* Swap the buffers (double buffering). */
ThisRight = NextRight;
NextRight = AudioRight -> ioa_Data;
/* Reset the audio data fill counter. */
FillCounter = 0;
/* Stop playing any sound (= ^S). */
AudioControl -> ioa_Request . io_Command = CMD_STOP;
AudioControl -> ioa_Period = Rate;
BeginIO(AudioControl);
WaitIO(AudioControl);
/* Prepare to start both audio device requests,
* the CMD_STOP command will keep them from getting
* satisfied immediately.
*/
BeginIO(AudioLeft);
BeginIO(AudioRight);
/* Start playing both channels simultaneously (= ^Q). */
AudioControl -> ioa_Request . io_Command = CMD_START;
AudioControl -> ioa_Period = Rate;
BeginIO(AudioControl);
WaitIO(AudioControl);
/* Remember current state, so the cleanup code will
* know what to do.
*/
AudioUsed = AudioActive = TRUE;
Permit();
}
/* ProcessAudio():
*
* Process incoming audio data messages.
*/
VOID
ProcessAudio()
{
struct AudioMessage *Message;
LONG i,j;
WORD Left,Right;
/* Loop until done. */
while(Message = (struct AudioMessage *)GetMsg(ChildPort))
{
/* Process the single data blocks. */
for(i = 0 ; i < Message -> NumBlocks ; i++)
{
/* Process the samples. */
for(j = 0 ; j < 588 ; j++)
{
/* Well, a bit of explanation might be necessary
* to tell you why this code is discarding every
* second data block. Each incoming message
* supplies data for about one second of music or
* sound to be replayed. This accounts for
* 44,100 samples per second for each data packet.
* Unfortunately, the highest replay rate the Amiga
* audio hardware supports at this time of writing
* is about 30,000 samples per second. Instead of
* scaling the data packet size down to the maximum
* the amount of data is halved, thus reducing the
* replay rate required to 22,050 samples per second.
*/
if(j & 1)
{
/* Convert the digital audio data (little endian,
* sixteen bits per sample, signed).
*/
Left = (((WORD)Message -> Data[i] . Sample[j] . LeftMSB) << 8) | Message -> Data[i] . Sample[j] . LeftLSB;
Right = (((WORD)Message -> Data[i] . Sample[j] . RightMSB) << 8) | Message -> Data[i] . Sample[j] . RightLSB;
/* Scale the sixteen bit values down to eight bits. */
ThisLeft[FillCounter] = Left >> 8;
ThisRight[FillCounter] = Right >> 8;
/* One more byte in the buffer... */
FillCounter++;
}
}
}
/* Clean up. */
DeleteAudioMessage(Message);
/* Audio output still active? */
if(AudioActive)
{
WaitIO(AudioLeft);
WaitIO(AudioRight);
AudioActive = FALSE;
}
/* Are we to stop here? */
if(SetSignal(0,0) & SIG_KILL)
break;
else
{
/* Replay the data if available. */
if(FillCounter)
StartAudio();
}
}
}
/* ChildTask():
*
* The audio player task.
*/
VOID __saveds
ChildTask()
{
/* Remember task address. */
Child = SysBase -> ThisTask;
/* Create communications port. */
if(ChildPort = CreateMsgPort())
{
struct AudioMessage *Message;
/* Initialize the audio device. */
if(AudioInit())
{
ULONG Signals;
BOOL Terminate = FALSE;
/* Wait for handshake signal. */
Signal(Father,SIG_HANDSHAKE);
/* Loop until finished or stopped. */
for(;;)
{
/* Wait for a signal... */
Signals = Wait(SIG_CHILD | SIG_KILL | SIG_SYNC);
/* The last track to play? */
if(Signals & SIG_SYNC)
Terminate = TRUE;
/* Stop it, now! */
if(Signals & SIG_KILL)
break;
/* Process audio data. */
if(Signals & SIG_CHILD)
ProcessAudio();
/* Wake up the handler task. */
if(FatherWaiting)
Signal(Father,SIG_SYNC);
/* Wait until track is replayed? */
if(Terminate)
{
if(!QueueCount)
{
if(AudioActive)
{
WaitIO(AudioLeft);
WaitIO(AudioRight);
}
break;
}
}
}
/* Clean up. */
if(Terminate)
{
Forbid();
Signal(Father,SIG_HANDSHAKE);
Child = NULL;
}
}
/* Get rid of queued messages. */
while(Message = (struct AudioMessage *)GetMsg(ChildPort))
DeleteAudioMessage(Message);
/* Free the audio resources. */
AudioExit();
/* Delete the communications port. */
DeleteMsgPort(ChildPort);
ChildPort = NULL;
}
/* Finish the rest. */
if(Child)
{
Forbid();
Signal(Father,SIG_HANDSHAKE);
Child = NULL;
}
}
/* CloseAll():
*
* The big cleanup routine.
*/
VOID
CloseAll()
{
/* Shut down the scsi device driver. */
SCSIExit();
/* Remove the player task. */
if(Child)
{
Signal(Child,SIG_KILL);
Wait(SIG_HANDSHAKE);
}
/* Free the command line reader data. */
if(ArgsPtr)
{
FreeDosObject(DOS_RDARGS,ArgsPtr);
FreeArgs(ArgsPtr);
ArgsPtr = NULL;
}
/* Free the command line argument data. */
if(Arg)
{
FreeVec(Arg);
Arg = NULL;
}
/* Close the libraries. */
if(UtilityBase)
{
CloseLibrary(UtilityBase);
UtilityBase = NULL;
}
if(DOSBase)
{
CloseLibrary(DOSBase);
DOSBase = NULL;
}
}
/* OpenAll():
*
* Open the resources required and complain if anything
* goes wrong.
*/
BOOL
OpenAll()
{
STRPTR Device;
LONG Unit,i;
BOOL GotIt = FALSE;
/* Most important ;-) */
SysBase = *(struct ExecBase **)4;
/* So we know who we are. */
Father = (struct Process *)SysBase -> ThisTask;
/* Called from Workbench? Oops... */
if(!Father -> pr_CLI)
{
struct Message *Message;
/* Wait for startup message. */
WaitPort(&Father -> pr_MsgPort);
/* Pick it up. */
Message = GetMsg(&Father -> pr_MsgPort);
Forbid();
/* Return it and stop here. */
ReplyMsg(Message);
return(FALSE);
}
/* Open the libraries. */
if(!(DOSBase = (struct DosLibrary *)OpenLibrary("dos.library",37)))
return(FALSE);
if(!(UtilityBase = OpenLibrary("utility.library",37)))
{
Printf("CDDA: Could not open utility.library v37.\n");
return(FALSE);
}
/* Set up the semaphore. */
InitSemaphore(&QueueSemaphore);
/* Allocate command line reader data. */
if(!(Arg = (STRPTR *)AllocVec(sizeof(STRPTR) * ARGCOUNT,MEMF_ANY | MEMF_CLEAR)))
{
PrintFault(ERROR_NO_FREE_STORE,"CDDA");
return(FALSE);
}
if(!(ArgsPtr = AllocDosObjectTags(DOS_RDARGS,TAG_DONE)))
{
PrintFault(IoErr(),"CDDA");
return(FALSE);
}
/* Just a bit of documentation. */
ArgsPtr -> RDA_ExtHelp = "\nThis program replays digital audio data directly off a compact disc\n"
"using the Amiga audio hardware (in stereo where available). A special\n"
"type of CD-ROM drive is required since this program makes use of a\n"
"vendor unique command supported by Sony drives, such as the CDU-8003A\n"
"or the Apple CD-300 drive.\n\n"
"Please note that due to the work involved reading and converting the\n"
"audio data music may not always play smoothly. The program will also\n"
"consume large amounts of chip memory in order to supply the player\n"
"task with a contiguous stream of data (the maximum number of sound\n"
"queue packets can be adjusted using a command line parameter). Your\n"
"system may also experience heavy task loading since the incoming data\n"
"(more than 88K bytes per second) must be handled quickly and\n"
"efficiently in order to avoid `pops' and delays.\n\n"
"The command line template looks like this:\n\n"
"DEVICE/K,UNIT/K/N,TRACK/N,FROM/K/N,TO/K/N,NUM/K/N,MAX/K/N,VERBOSE/S\n\n"
"DEVICE This parameter requires a keyword. By default the program will\n"
" address `scsi.device' as the device driver which controls your\n"
" CD-ROM drive. You can override this by using the following\n"
" command line option:\n\n"
" DEVICE <device name>\n\n"
" Example: DEVICE gvpscsi.device\n\n"
"UNIT This parameter requires a keyword. By default the program will\n"
" expect your CD-ROM drive to respond to device unit number 2.\n"
" You can override this by using the following command line\n"
" options:\n\n"
" UNIT <unit number>\n\n"
" Example: UNIT 3\n\n"
"TRACK This parameter requires a keyword. Each data/music track on\n"
" the CD has a unique number assigned which ranges from 1 to 99.\n"
" This is how you choose which track to replay.\n\n"
" Example: TRACK 1\n\n"
"FROM This parameter requires a keyword. The CD consists of several\n"
" sectors, each of which can be addressed independently. Several\n"
" sectors form a track, 75 sectors of data make up one second of\n"
" audio data. This is how you choose which sectors to replay,\n"
" note that this parameter requires the \"TO\" or the \"NUM\"\n"
" parameter.\n\n"
" Example: FROM 1 TO 600\n\n"
"TO This parameter requires a keyword. It works both with the\n"
" \"TRACK\" and \"FROM\" parameters.\n\n"
" Example: TRACK 1 TO 5 Plays tracks to 1 to 5\n"
" FROM 1 TO 600 Plays sectors 1 to 600\n\n"
"NUM This parameter requires a keyword. It works both with the\n"
" \"TRACK\" and \"FROM\" parameters.\n\n"
" Example: TRACK 3 NUM 7 Plays tracks to 3 to 10\n"
" FROM 2 NUM 4 Plays sectors 2 to 6\n\n"
"MAX This parameter requires a keyword. In order to feed a\n"
" contiguous stream of data to the player task incoming audio\n"
" data is queued. The maximum number of data packets queued is\n"
" set using this parameter. By default the number of packets is\n"
" set to 3. Each packet requires about 176K bytes, so take care.\n\n"
" Example: MAX 5\n\n"
"VERBOSE This command line option will enable some more or less helpful\n"
" progress messages, may not be of any use at all.\n\n"
" Example: VERBOSE\n\n"
"That's all folks! This program and the accompanying source code are\n"
"freeware. Respect copyright, encourage creativity.\n\n"
TEMPLATE;
/* Read the command line arguments. */
if(!ReadArgs(TEMPLATE,(LONG *)Arg,ArgsPtr))
{
PrintFault(IoErr(),"CDDA");
return(FALSE);
}
/* Did we get what we need? */
if((!Arg[ARG_TRACK] && !Arg[ARG_FROM]) || (Arg[ARG_FROM] && !Arg[ARG_TO] && !Arg[ARG_NUM]))
{
PrintFault(ERROR_REQUIRED_ARG_MISSING,"CDDA");
return(FALSE);
}
/* Get the scsi device name. */
if(Arg[ARG_DEVICE])
Device = Arg[ARG_DEVICE];
else
Device = SCSI_DEVICE;
/* Get the scsi device unit number. */
if(Arg[ARG_UNIT])
Unit = *(LONG *)Arg[ARG_UNIT];
else
Unit = SCSI_UNIT;
/* Verbose operation? */
if(Arg[ARG_VERBOSE])
Verbose = TRUE;
else
Verbose = FALSE;
/* Create the player task. */
Forbid();
if(CreateTask("« CDDA Player task »",CHILD_PRI,ChildTask,CHILD_STACK))
{
SetSignal(0,SIG_HANDSHAKE);
Wait(SIG_HANDSHAKE);
}
Permit();
if(!Child)
{
Printf("CDDA: Failed to launch player task.\n");
return(FALSE);
}
/* Set up the scsi device driver. */
if(!SCSIInit(Device,Unit))
{
Printf("CDDA: Failed to open \"%s\" unit #%ld.\n",Device,Unit);
return(FALSE);
}
/* Get things going, after a medium is removed/inserted
* the first command will always fail.
*/
TestUnitReady();
/* Inquire device parameters. */
if(Inquire())
{
Printf("CDDA: Cannot read device parameters of \"%s\" unit #%ld.\n",Device,Unit);
return(FALSE);
}
/* We need a device to conform to the SCSI-2 specs, or we
* will be unable to read the table of contents.
*/
if((InquiryData . Version & 0x07) < 2)
{
Printf("CDDA: CD-ROM device must conform to the SCSI-2 specification..\n");
return(FALSE);
}
/* Is this really a CD-ROM drive? */
if((InquiryData . PeripheralType & 0x1F) != 0x05)
{
Printf("CDDA: \"%s\" unit #%ld is not a CD-ROM device.\n",Device,Unit);
return(FALSE);
}
/* Is there a CD present in the drive? */
if(TestUnitReady())
{
Printf("CDDA: No CD in \"%s\" unit #%ld\n",Device,Unit);
return(FALSE);
}
/* Read the table of contents. */
if(ReadTOC())
{
Printf("CDDA: Failed to read table of contents.\n");
return(FALSE);
}
/* Track number given? */
if(Arg[ARG_TRACK])
{
LONG Track = *(LONG *)Arg[ARG_TRACK],To;
/* Track number too low? */
if(Track < FullTOC . FirstTrack)
{
Printf("CDDA: The first available track is #%ld, not #%ld.\n",FullTOC . FirstTrack,Track);
return(FALSE);
}
/* Track number too high? */
if(Track > FullTOC . LastTrack)
{
Printf("CDDA: There are only %ld tracks on this CD, not %ld.\n",FullTOC . LastTrack,Track);
return(FALSE);
}
/* Stop track given? */
if(Arg[ARG_TO])
To = *(LONG *)Arg[ARG_TO];
else
{
/* Number of tracks to replay given? */
if(Arg[ARG_NUM])
To = Track + *(LONG *)Arg[ARG_NUM] - 1;
else
To = Track;
}
/* Make sure that the stop value makes sense. */
if(To < Track)
To = Track;
if(To > FullTOC . LastTrack)
To = FullTOC . LastTrack;
/* Find the correct sector offsets. */
for(i = 0 ; i < FullTOC . LastTrack - FullTOC . FirstTrack + 1 ; i++)
{
if(FullTOC . TOC[i] . TrackNumber == Track)
{
Index = FullTOC . TOC[i] . Address;
Total = FullTOC . TOC[i + (To - Track) + 1] . Address - FullTOC . TOC[i] . Address;
GotIt = TRUE;
break;
}
}
/* Successful? */
if(!GotIt)
{
Printf("CDDA: Failed to find track #%ld.\n",*(LONG *)Arg[ARG_TRACK]);
return(FALSE);
}
}
else
{
/* Get the first sector number to play. */
Index = *(LONG *)Arg[ARG_FROM];
/* Are we to replay a number of sectors
* or are we to replay the audio data
* between a start and a stop sector?
*/
if(Arg[ARG_NUM])
Total = *(LONG *)Arg[ARG_NUM];
else
Total = *(LONG *)Arg[ARG_TO] - Index;
}
GotIt = FALSE;
/* Verify the offsets given. */
for(i = FullTOC . LastTrack - FullTOC . FirstTrack ; i >= 0 ; i--)
{
/* Is this the track we want? */
if(FullTOC . TOC[i] . Address <= Index)
{
LONG Size;
/* No chance to replay raw data, this could
* be a CD-ROM track.
*/
if(FullTOC . TOC[i] . Control & (1 << 2))
{
Printf("CDDA: Can only replay digital audio data.\n");
return(FALSE);
}
/* No quadraphonics, please. */
if(FullTOC . TOC[i] . Control & (1 << 3))
{
Printf("CDDA: Can only replay two channel audio data.\n");
return(FALSE);
}
GotIt = TRUE;
/* Calculate the number of sectors involved. */
Size = FullTOC . TOC[i + 1] . Address - FullTOC . TOC[i] . Address;
/* Too many requested? */
if(Total > Size)
{
if(Verbose)
Printf("CDDA: Too many sectors requested, truncating.\n");
Total = Size;
}
break;
}
}
if(!GotIt)
{
Printf("CDDA: Failed to find sector #%ld.\n",Index);
return(FALSE);
}
return(TRUE);
}
/* ReadCDDASectors(APTR Buffer,LONG From,LONG Count):
*
* Read digital audio data.
*/
BYTE
ReadCDDASectors(APTR Buffer,LONG From,LONG Count)
{
STATIC struct ReadCDDA ReadCDDA;
/* Set up the read command. */
ReadCDDA . Command = 0xD8;
ReadCDDA . Pad = 0;
ReadCDDA . Address = From;
ReadCDDA . Count = Count;
ReadCDDA . SubCode = 0;
ReadCDDA . Control = 0;
/* Set up the SCSI direct command. */
SCSIRequest -> iotd_Req . io_Command = HD_SCSICMD;
SCSIRequest -> iotd_Req . io_Data = &SCSICmd;
SCSIRequest -> iotd_Req . io_Length = sizeof(struct SCSICmd);
SCSICmd . scsi_Data = Buffer;
SCSICmd . scsi_Length = Count * sizeof(struct CDDASector);
SCSICmd . scsi_Actual = 0;
SCSICmd . scsi_Command = (UBYTE *)&ReadCDDA;
SCSICmd . scsi_CmdLength = sizeof(struct ReadCDDA);
SCSICmd . scsi_CmdActual = 0;
SCSICmd . scsi_Flags = SCSIF_READ | SCSIF_AUTOSENSE;
SCSICmd . scsi_SenseData = SenseBuffer;
SCSICmd . scsi_SenseLength = 255;
SCSICmd . scsi_SenseActual = 0;
return(DoIO(SCSIRequest));
}
/* TestUnitReady():
*
* Check to see whether the unit thinks it is ready
* to receive and process commands or not.
*/
BYTE
TestUnitReady()
{
STATIC struct TestUnitReady TestUnitReady;
/* Set up the test command (all zeroes). */
memset(&TestUnitReady,0,sizeof(struct TestUnitReady));
/* Set up the SCSI direct command. */
SCSIRequest -> iotd_Req . io_Command = HD_SCSICMD;
SCSIRequest -> iotd_Req . io_Data = &SCSICmd;
SCSIRequest -> iotd_Req . io_Length = sizeof(struct SCSICmd);
SCSICmd . scsi_Length = 0;
SCSICmd . scsi_Actual = 0;
SCSICmd . scsi_Command = (UBYTE *)&TestUnitReady;
SCSICmd . scsi_CmdLength = sizeof(struct TestUnitReady);
SCSICmd . scsi_CmdActual = 0;
SCSICmd . scsi_Flags = SCSIF_READ | SCSIF_AUTOSENSE;
SCSICmd . scsi_SenseData = SenseBuffer;
SCSICmd . scsi_SenseLength = 255;
SCSICmd . scsi_SenseActual = 0;
return(DoIO(SCSIRequest));
}
/* ReadTOC():
*
* Read the table of contents.
*/
BYTE
ReadTOC()
{
STATIC struct ReadTOC ReadTOC;
/* Set up the read command. */
memset(&ReadTOC,0,sizeof(struct ReadTOC));
ReadTOC . Command = 0x43;
ReadTOC . Alloc1 = sizeof(struct FullTOC) >> 8;
ReadTOC . Alloc2 = sizeof(struct FullTOC) & 0xFF;
/* Set up the SCSI direct command. */
SCSIRequest -> iotd_Req . io_Command = HD_SCSICMD;
SCSIRequest -> iotd_Req . io_Data = &SCSICmd;
SCSIRequest -> iotd_Req . io_Length = sizeof(struct SCSICmd);
SCSICmd . scsi_Data = (UWORD *)&FullTOC;
SCSICmd . scsi_Length = sizeof(struct FullTOC);
SCSICmd . scsi_Actual = 0;
SCSICmd . scsi_Command = (UBYTE *)&ReadTOC;
SCSICmd . scsi_CmdLength = sizeof(struct ReadTOC);
SCSICmd . scsi_CmdActual = 0;
SCSICmd . scsi_Flags = SCSIF_READ | SCSIF_AUTOSENSE;
SCSICmd . scsi_SenseData = SenseBuffer;
SCSICmd . scsi_SenseLength = 255;
SCSICmd . scsi_SenseActual = 0;
return(DoIO(SCSIRequest));
}
/* Inquire():
*
* Inquire hard-coded device parameters.
*/
BYTE
Inquire()
{
STATIC struct Inquiry Inquiry;
/* Set up the inquiry command. */
memset(&Inquiry,0,sizeof(struct Inquiry));
Inquiry . Command = 0x12;
Inquiry . AllocationLength = sizeof(struct InquiryData);
/* Set up the SCSI direct command. */
SCSIRequest -> iotd_Req . io_Command = HD_SCSICMD;
SCSIRequest -> iotd_Req . io_Data = &SCSICmd;
SCSIRequest -> iotd_Req . io_Length = sizeof(struct SCSICmd);
SCSICmd . scsi_Data = (UWORD *)&InquiryData;
SCSICmd . scsi_Length = sizeof(struct InquiryData);
SCSICmd . scsi_Actual = 0;
SCSICmd . scsi_Command = (UBYTE *)&Inquiry;
SCSICmd . scsi_CmdLength = sizeof(struct Inquiry);
SCSICmd . scsi_CmdActual = 0;
SCSICmd . scsi_Flags = SCSIF_READ | SCSIF_AUTOSENSE;
SCSICmd . scsi_SenseData = SenseBuffer;
SCSICmd . scsi_SenseLength = 255;
SCSICmd . scsi_SenseActual = 0;
return(DoIO(SCSIRequest));
}